import { ElMessage } from "element-plus"
import { showToast } from "vant"
import { Utils } from "./utils"
import { adminStore, loadingStore } from "../../store/index";
import imageCompression from 'browser-image-compression'

const isPhone = window.isPhone
interface interResponseProp {
    m: string, c: string, d: string
}
class PendingTask {
    pendingTask: Map<string, Promise<any>> = new Map()
    cache: Map<string, any> = new Map()

    deleteItem(key: string) {
        this.pendingTask.delete(key)
        if (!this.pendingTask.size) {
            loadingStore().setLoading(false)
        }
    }
    addItem(key: string, value: Promise<any>, unLoadding?: boolean) {
        this.pendingTask.set(key, value)
        if (this.pendingTask.size && !unLoadding) {
            loadingStore().setLoading(true)
        }
    }
    hasItem(key: string) {
        return this.pendingTask.has(key)
    }
    getItem(key: string) {
        return this.pendingTask.get(key)
    }
}
//防止同一个接口多次访问
const pendingTask = new PendingTask()

export class Http extends Utils {

    cache: Map<string, any> = new Map<string, any>();
    baseUrl: string = ''

    #gettoken() {
        return 'Bearer ' + localStorage.getItem('token') || ''
    }
    async imgUpload(fileUrl: string | File): Promise<string> {
        if (!fileUrl) return '';
        let file: File;
        const max_size = 1024 * 1024 * 2;
        if (typeof fileUrl === "string") {
            const blob = await this.fetchBlob(fileUrl);
            file = new File([blob], Date.now() + Math.floor(Math.random() * 1000) + "." + blob.type.replace(/.*\//, ''), { type: 'image/*' });
        } else {
            file = fileUrl;
        }
        if (file.size > max_size) {
            file = await this.compressImage(file);
        }
        const form = new FormData();
        form.append('file', file);
        const header: HeadersInit & { Authorization?: string } = {}
        const token = this.#gettoken()
        if (token) header.Authorization = token
        const res = await fetch('/api' + '/upload', {
            method: 'POST',
            headers: header,
            body: form
        });
        return await this.#handleUploadResponse(res);
    }
    async fetchBlob(fileUrl: string): Promise<Blob> {
        const res = await fetch(fileUrl);
        return await res.blob();
    }
    async compressImage(file: File): Promise<File> {
        const options = {
            maxSizeMB: 10,
            maxWidthOrHeight: 1024,
            useWebWorker: true
        };
        const blob = await imageCompression(file, options);
        return new File([blob], Math.floor(Math.random() * 1000) + "." + blob.type.replace(/.*\//, ''), { type: 'image/*' });
    }
    async #handleUploadResponse(res: Response): Promise<string> {
        const data = await res.json();
        const { errorCode, msg } = data;
        if (!errorCode) {
            return data.data;
        } else {
            this.#showMessage(msg);
            return 'error';
        }
    }
    #handleRequeastReadCache(Url: string, cache?: 'cache' | 'recache' | 'global' | 'recacheGlobal') {
        let caches = this.cache
        if (cache === 'global' || cache === 'recacheGlobal') {
            caches = pendingTask.cache
        }
        if (cache) {
            if (cache === 'recache' || cache === 'recacheGlobal') {
                caches.delete(Url)
            } else if (cache === 'cache' || cache === 'global') {
                if (caches.has(Url)) {
                    return caches.get(Url)
                }
            }
        }
        return null
    }
    #handleRequestHasPending(Url: string) {
        if (pendingTask.hasItem(Url)) {
            return pendingTask.getItem(Url)
        }
    }
    #handleRequestSetPending(Url: string, unLoadding?: boolean) {
        pendingTask.addItem(Url, Promise.resolve(), unLoadding);
    }
    #handleRequestSetPendingValue(Url: string, value: Promise<any>) {
        pendingTask.pendingTask.set(Url, value)
    }
    #handleRequestDeletePending(Url: string) {
        pendingTask.deleteItem(Url)
    }
    #handleRequestWirteCache(Url: string, data: any, cache?: 'cache' | 'recache' | 'global' | 'recacheGlobal') {
        let caches = this.cache
        if (cache === 'global' || cache === 'recacheGlobal') {
            caches = pendingTask.cache
        }
        if (data && cache) {
            caches.set(Url, data);
        }
    }
    async #myFetch(url: string, method?: string, data?: object | FormData, headers?: HeadersInit, timeout: number = 3000): Promise<any> {
        const header: HeadersInit & { Authorization?: string } = { 'Content-Type': 'application/json', 'accept': 'application/json', ...headers }
        const token = this.#gettoken()
        if (token) header.Authorization = token
        const res = await fetch(url, {
            headers: header,
            method: method || "POST", body: JSON.stringify(data),
        })
        if (res.status === 200) return await Promise.race([res.json(), new Promise((res) => setTimeout(() => { res({ code: '000', message: '请求超时' }) }, timeout))])
        return res
    }

    async request(url: string, {
        headers, method, data, parames, unShowToast, unLoadding, cache, successToast, timeout = 3000
    }: {
        headers?: HeadersInit, method?: string, data?: object | FormData, parames?: Record<string, string | number | boolean | object | null | undefined>,
        unShowToast?: boolean, unLoadding?: boolean, cache?: 'recache' | 'cache' | 'global' | 'recacheGlobal', successToast?: boolean, timeout?: number
    } = {}): Promise<any> {
        const request = async () => {
            const res = await this.#myFetch(Url, method, data, headers, timeout);
            if (!res) { this.#showMessage('服务器异常'); return null }
            if (res.status === 401) {
                return await this.#reLogin(url, { headers, method, data, parames, unShowToast, unLoadding, cache, successToast, timeout })
            }
            return this.#handleRequeastResponse(res, unShowToast, successToast);
        }
        const Url = '/api' + (parames ? this.utils_parames(parames, url) : url)
        const lastPromise = this.#handleRequestHasPending(Url)
        this.#handleRequestSetPending(Url, unLoadding);
        if (lastPromise) {
            this.#handleRequestDeletePending(Url);
            return await lastPromise
        }
        const cacheData = this.#handleRequeastReadCache(Url, cache);
        if (cacheData) {
            this.#handleRequestDeletePending(Url);
            return cacheData
        }
        const promise = request()
        this.#handleRequestSetPendingValue(Url, promise);
        const res = await promise;
        this.#handleRequestDeletePending(Url);
        this.#handleRequestWirteCache(Url, res, cache);
        return res;
    }
    async #reLogin(url: string, {
        headers, method, data, parames, unShowToast, unLoadding, cache, successToast, timeout = 3000
    }: {
        headers?: HeadersInit, method?: string, data?: object | FormData, parames?: Record<string, string | number | boolean | object | null | undefined>,
        unShowToast?: boolean, unLoadding?: boolean, cache?: 'recache' | 'cache' | 'global' | 'recacheGlobal', successToast?: boolean, timeout?: number
    } = {}) {
        const res = await adminStore().reLogin();
        if (res) return await this.request(url, { headers, method, data, parames, unShowToast, unLoadding, cache, successToast, timeout })
    }
    #handleRequeastResponse(
        res: any, unShowToast: boolean = false, successToast: boolean = false,
        prop: interResponseProp = { m: 'message', c: 'code', d: 'data' }
    ) {
        const { m, c, d } = prop
        const { [m]: message, [c]: code, [d]: data } = res;
        if (code === 200) {
            if (successToast) {
                this.#showMessage(message, 'success')
            }
            return data || 'success';
        } else {
            if (unShowToast) return;
            this.#showMessage(message);
        }
    }
    /**
     * 显示消息
     * @param msg 消息内容
     * @param type 消息类型
     */
    #showMessage(msg: string | string[], type: 'error' | 'success' = 'error') {
        if (!msg) return
        if (Http.toasting && type === 'error') return
        Http.toasting = true
        setTimeout(() => {
            Http.toasting = false
        }, 2000);
        if (Array.isArray(msg)) {
            if (isPhone) {
                showToast(msg[0]);
            } else {
                ElMessage[type](msg[0]);
            }
        } else {
            if (isPhone) {
                showToast(msg);
            } else {
                ElMessage[type](msg);
            }
        }
    }
    /**
     * 并发请求
     * @param num 并发请求数量
     * @param url 请求url
     * @param data 请求数据
     * @returns  Promise<any[]>
     */
    async concurrent(num: number, url: string, data: object[]): Promise<any[]> {
        const result: any[] = []
        let k = 0
        if (num > data.length) num = data.length
        const requestNext = async (i: number) => {
            const res = await this.request(url + '?current=' + i, { data: data[k] })
            result[k] = res
            k++
            if (k <= data.length) return requestNext(i)
        }
        const promises = Array(num).fill(0).map((v, i) => {
            return requestNext(i)
        })
        await Promise.all(promises)
        return result
    }
    //同时只存在一条提示信息
    static toasting: boolean = false
}